/*
*    Reversi Contender homepage:  http://code.google.com/p/reversi-contender
*
*    copyright 2011 steven sims <steven.joseph.sims at gmail.com>
*
*    This file is part of Reversi Contender.
*
*    Reversi Contender is free software: you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    Reversi Contender is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License
*    along with Reversi Contender.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.threadswarm.reversi;

import java.awt.Point;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.EnumMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingDeque;

/**
 *
 * @author steve
 */
public class ReversiGameImp implements ReversiGame, Serializable {

    private final static List<Point> offsetList = Arrays.asList(new Point(-1,-1), new Point(-1,0), new Point(-1,1), new Point(0,-1), new Point(0,1),
            new Point(1,-1), new Point(1,0), new Point(1,1));
    private final Deque<Cell[][]> boardStack = new LinkedBlockingDeque<Cell[][]>();
    private Cell[][] board;
    private Status currentColor;
    private Map<Status, Player> playerMap = new EnumMap<Status, Player>(Status.class);
    private final Map<Point, BoardChangeListener> boardListenerMap = new ConcurrentHashMap<Point, BoardChangeListener>();
    private boolean previousPassed = false;

    public ReversiGameImp() {
        board = getNewBoard();
        currentColor = Status.BLACK;
    }

    public ReversiGameImp(ReversiGame originalGame) {
        currentColor = originalGame.getCurrentStatus();
        board = originalGame.getBoardCopy();
    }

    @Override
    public synchronized List<Cell> getAffectedCells(Point targetPoint, Status barrierStatus) {
        List<Cell> affectedCells;
        if(board[targetPoint.x][targetPoint.y].getStatus() != Status.EMPTY){
            affectedCells = Collections.emptyList();
        }else{
            affectedCells = new LinkedList<Cell>();
            List<Cell> accumList = new LinkedList<Cell>();
            for(Point offset : offsetList){
                accumList.clear();
                int x = offset.x;
                int y = offset.y;
                Cell blockingCell = null;
                while(true){
                    try{
                        Cell cell = board[targetPoint.x + x][targetPoint.y + y];
                        if(cell.getStatus() == Status.EMPTY)
                            break;
                        if(cell.getStatus() == barrierStatus){
                            blockingCell = cell;
                            break;
                        }
                        accumList.add(cell);
                        x += offset.x;
                        y += offset.y;
                    }catch(ArrayIndexOutOfBoundsException ex){
                        break;
                    }
                }
                if(blockingCell != null){
                    affectedCells.addAll(accumList);
                }
            }
        }
        return affectedCells;
    }

    @Override
    public synchronized List<Point> getAvailableInsertions() {
        List<Point> availableInsertions = new LinkedList<Point>();
        for(int x = 0; x < 8; x++){
            for(int y = 0; y < 8; y++){
                Cell cell = board[x][y];
                if(cell.getStatus() == Status.EMPTY){
                    List<Cell> affectedList = getAffectedCells(cell.getPoint(), currentColor);
                    if(!affectedList.isEmpty())
                        availableInsertions.add(cell.getPoint());
                }
            }
        }
        return availableInsertions;
    }

    @Override
    public List<Point> insert(Point point) {
        return insert(point, currentColor);
    }

    @Override
    public synchronized List<Point> insert(Point point, Status status) {
        if(status != currentColor)
            throw new IllegalArgumentException("This move belongs to " + currentColor + ", you tried to insert " + status);
        Cell targetCell = board[point.x][point.y];
        if(targetCell.getStatus() != Status.EMPTY)
            throw new IllegalArgumentException("A value was previously assigned to cell (" + point.x + "," + point.y + ")");
        boardStack.addFirst(getBoardCopy());
        List<Cell> affectedCells = getAffectedCells(point, status);
        List<Point> affectedPoints = new ArrayList<Point>(affectedCells.size());
        targetCell.setStatus(status);
        for(Cell cell : affectedCells){
            cell.setStatus(status);
            affectedPoints.add(cell.getPoint());
        }

        if(!boardListenerMap.isEmpty()){
            for(Point p : affectedPoints){
                BoardChangeListener changeListener = boardListenerMap.get(p);
                if(changeListener != null)
                    changeListener.boardChangeEvent();
            }
            BoardChangeListener insertedPointListener = boardListenerMap.get(point);
            if(insertedPointListener != null)
                insertedPointListener.boardChangeEvent();
        }

        previousPassed = false;
        incrementTurn();
        
        return affectedPoints;
    }

    private void incrementTurn() {
        currentColor = (currentColor == Status.WHITE)? Status.BLACK : Status.WHITE;

        Player player = playerMap.get(currentColor);
        if(player != null)
            player.turnAcquired();
    }

    @Override
    public synchronized void undo() {
        if(!boardStack.isEmpty()){
            board = boardStack.removeFirst();
            incrementTurn();
        }
    }

    @Override
    public synchronized void reset() {
        boardStack.clear();
        board = getNewBoard();
        currentColor = Status.BLACK;
        Player player = playerMap.get(currentColor);
        if(player != null)
            player.turnAcquired();
    }

    @Override
    public synchronized Status getCurrentStatus() {
        return currentColor;
    }

    @Override
    public void addBoardChangeListener(Point point, BoardChangeListener listener) {
        boardListenerMap.put(point, listener);
    }

    private static Cell[][] getNewBoard() {
        Cell[][] freshBoard = new Cell[8][8];
        for(int x = 0; x < 8; x++){
            for(int y = 0; y < 8; y++)
                freshBoard[x][y] = new Cell(new Point(x, y), Status.EMPTY);
        }
        //initialize default configuration
        freshBoard[3][3].setStatus(Status.WHITE);
        freshBoard[3][4].setStatus(Status.BLACK);
        freshBoard[4][3].setStatus(Status.BLACK);
        freshBoard[4][4].setStatus(Status.WHITE);

        return freshBoard;
    }

    @Override
    public synchronized Cell[][] getBoardCopy() {
        Cell[][] boardCopy = new Cell[8][8];
        for(int x = 0; x < 8; x++)
            for(int y = 0; y < 8; y++)
                boardCopy[x][y] = board[x][y].copyOf();
        return boardCopy;
    }

    @Override
    public synchronized Cell getCellCopy(Point cellCoordinates) {
        return board[cellCoordinates.x][cellCoordinates.y].copyOf();
    }

    @Override
    public synchronized Map<Status, Integer> getCountMap() {
        Map<Status, Integer> countMap = new EnumMap<Status, Integer>(Status.class);
        countMap.put(Status.BLACK, 0);
        countMap.put(Status.WHITE, 0);
        countMap.put(Status.EMPTY, 0);
        for(int x = 0; x < 8; x++){
            for(int y = 0; y < 8; y++){
                Cell cell = board[x][y];
                Integer count = countMap.get(cell.getStatus());
                count++;
                countMap.put(cell.getStatus(), count);
            }
        }
        return countMap;
    }

    @Override
    public synchronized void addPlayer(Player player, Status status) {
        playerMap.put(status, player);
    }

    @Override
    public synchronized void start() {
        playerMap.get(Status.BLACK).turnAcquired();
    }

    @Override
    public synchronized void pass() {
        previousPassed = true;
        incrementTurn();
    }

    @Override
    public synchronized boolean isPreviousPassed() {
        return previousPassed;
    }

    public static class GameFinishedException extends RuntimeException {

        private final Status winner;

        public GameFinishedException(Status winner) {
            this.winner = winner;
        }

        public Status getWinner() {
            return winner;
        }
    }

}
